{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "multi_worker_with_keras.ipynb",
      "version": "0.3.2",
      "provenance": [],
      "private_outputs": true,
      "collapsed_sections": [],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Tce3stUlHN0L"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "tuOe1ymfHZPu",
        "colab": {},
        "cellView": "form"
      },
      "source": [
        "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "# you may not use this file except in compliance with the License.\n",
        "# You may obtain a copy of the License at\n",
        "#\n",
        "# https://www.apache.org/licenses/LICENSE-2.0\n",
        "#\n",
        "# Unless required by applicable law or agreed to in writing, software\n",
        "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "# See the License for the specific language governing permissions and\n",
        "# limitations under the License."
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "MfBg1C5NB3X0"
      },
      "source": [
        "# 케라스를 사용한 다중 워커(Multi-worker) 훈련\n",
        "\n",
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/distribute/multi_worker_with_keras\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />TensorFlow.org에서 보기</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/ko/tutorials/distribute/multi_worker_with_keras.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />구글 코랩(Colab)에서 실행하기</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/ko/tutorials/distribute/multi_worker_with_keras.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />깃허브(GitHub) 소스 보기</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "FcjoxbKVT3r-",
        "colab_type": "text"
      },
      "source": [
        "Note: 이 문서는 텐서플로 커뮤니티에서 번역했습니다. 커뮤니티 번역 활동의 특성상 정확한 번역과 최신 내용을 반영하기 위해 노력함에도 불구하고 [공식 영문 문서](https://github.com/tensorflow/docs/blob/master/site/en/tutorials/distribute/multi_worker_with_keras.ipynb)의 내용과 일치하지 않을 수 있습니다. 이 번역에 개선할 부분이 있다면 [tensorflow/docs](https://github.com/tensorflow/docs) 깃허브 저장소로 풀 리퀘스트를 보내주시기 바랍니다. 문서 번역이나 리뷰에 참여하려면 [docs-ko@tensorflow.org](https://groups.google.com/a/tensorflow.org/forum/#!forum/docs-ko)로 메일을 보내주시기 바랍니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "xHxb-dlhMIzW"
      },
      "source": [
        "## 개요\n",
        "\n",
        "이 튜토리얼에서는 `tf.distribute.Strategy` API를 사용하여 케라스 모델을 다중 워커로 분산 훈련하는 방법을 살펴보겠습니다. 다중 워커를 사용하여 훈련할 수 있도록 전략을 디자인했기 때문에, 단일 워커 훈련용으로 만들어진 케라스 모델도 코드를 조금만 바꾸면 다중 워커를 사용하여 훈련할 수 있습니다.\n",
        "\n",
        "`tf.distribute.Strategy` API에 관심이 있으신 분들은 [텐서플로로 분산 훈련하기](../../guide/distributed_training.ipynb) 가이드에서 텐서플로가 제공하는 분산 훈련 전략들을 훑어보실 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "MUXex9ctTuDB"
      },
      "source": [
        "## 설정\n",
        "\n",
        "먼저, 텐서플로를 설정하고 필요한 패키지들을 가져옵니다."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "IqR2PQG4ZaZ0",
        "colab": {}
      },
      "source": [
        "from __future__ import absolute_import, division, print_function, unicode_literals"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "bnYxvfLD-LW-",
        "colab": {}
      },
      "source": [
        "try:\n",
        "  # %tensorflow_version 기능은 코랩에서만 사용할 수 있습니다.\n",
        "  %tensorflow_version 2.x\n",
        "except Exception:\n",
        "  pass\n",
        "import tensorflow_datasets as tfds\n",
        "import tensorflow as tf\n",
        "tfds.disable_progress_bar()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "hPBuZUNSZmrQ"
      },
      "source": [
        "## 데이터셋 준비하기\n",
        "\n",
        "MNIST 데이터셋을 [TensorFlow Datasets](https://www.tensorflow.org/datasets)에서 받아옵시다. [MNIST 데이터셋](http://yann.lecun.com/exdb/mnist/)은 0-9 숫자를 손으로 쓴 28x28 픽셀 흑백 이미지입니다. 6만 개의 훈련 샘플과 만 개의 테스트 샘플이 들어있습니다."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "dma_wUAxZqo2",
        "colab": {}
      },
      "source": [
        "BUFFER_SIZE = 10000\n",
        "BATCH_SIZE = 64\n",
        "\n",
        "# MNIST 데이터를 (0, 255] 범위에서 (0., 1.] 범위로 조정\n",
        "def scale(image, label):\n",
        "  image = tf.cast(image, tf.float32)\n",
        "  image /= 255\n",
        "  return image, label\n",
        "\n",
        "datasets, info = tfds.load(name='mnist',\n",
        "                           with_info=True,\n",
        "                           as_supervised=True)\n",
        "\n",
        "train_datasets_unbatched = datasets['train'].map(scale).cache().shuffle(BUFFER_SIZE)\n",
        "train_datasets = train_datasets_unbatched.batch(BATCH_SIZE)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "o87kcvu8GR4-"
      },
      "source": [
        "## 케라스 모델 만들기\n",
        "`tf.keras.Sequential` API를 사용하여 간단한 합성곱 신경망 케라스 모델을 만들고 컴파일하도록 하겠습니다. 우리 MNIST 데이터셋으로 훈련시킬 모델입니다.\n",
        "\n",
        "Note: 케라스 모델을 만드는 절차는 [텐서플로 케라스 가이드](https://www.tensorflow.org/guide/keras#sequential_model)에서 더 상세하게 설명하고 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "aVPHl0SfG2v1",
        "colab": {}
      },
      "source": [
        "def build_and_compile_cnn_model():\n",
        "  model = tf.keras.Sequential([\n",
        "      tf.keras.layers.Conv2D(32, 3, activation='relu', input_shape=(28, 28, 1)),\n",
        "      tf.keras.layers.MaxPooling2D(),\n",
        "      tf.keras.layers.Flatten(),\n",
        "      tf.keras.layers.Dense(64, activation='relu'),\n",
        "      tf.keras.layers.Dense(10, activation='softmax')\n",
        "  ])\n",
        "  model.compile(\n",
        "      loss=tf.keras.losses.sparse_categorical_crossentropy,\n",
        "      optimizer=tf.keras.optimizers.SGD(learning_rate=0.001),\n",
        "      metrics=['accuracy'])\n",
        "  return model"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "2UL3kisMO90X"
      },
      "source": [
        "먼저 단일 워커를 이용하여 적은 수의 에포크만큼만 훈련을 해보고 잘 동작하는지 확인해봅시다. 에포크가 넘어감에 따라 손실(loss)은 줄어들고 정확도는 1.0에 가까워져야 합니다."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "6Qe6iAf5O8iJ",
        "colab": {}
      },
      "source": [
        "single_worker_model = build_and_compile_cnn_model()\n",
        "single_worker_model.fit(x=train_datasets, epochs=3)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "8YFpxrcsZ2xG"
      },
      "source": [
        "## 다중 워커 구성\n",
        "\n",
        "자 이제 다중 워커 훈련의 세계로 들어가 봅시다. 텐서플로에서 여러 장비를 사용할 때는 `TF_CONFIG` 환경 변수를 설정해야 합니다. 하나의 클러스터를 구성하는 각 장비에 클러스터 구성을 알려주고 각각 다른 역할을 부여하기 위해 `TF_CONFIG`를 사용합니다.\n",
        "\n",
        "`TF_CONFIG`는 `cluster`와 `task` 두 개의 부분으로 구성됩니다. `cluster`에는 훈련 클러스터에 대한 정보를 지정합니다. `worker` 같은 여러 타입의 작업 이름을 키로 하는 파이썬 딕셔너리를 지정합니다. 다중 워커 훈련에서는 보통 일반적인 워커보다 조금 더 많은 일을 하는 특별한 워커가 하나 필요합니다. 이 워커는 체크포인트를 저장하거나, 서머리(summary)를 쓰는 일 등을 추가로 담당하게 됩니다. 보통 치프('chief') 워커라고 부르고, 관례적으로 `index` 번호가 0인 워커가 치프 워커가 됩니다(사실 `tf.distribute.Strategy`가 이렇게 구현되었습니다). 한편 `task`에는 현재 워커의 작업에 대한 정보를 지정합니다.\n",
        "\n",
        "이 예에서는 작업(task) `type`을 `\"worker\"`로 지정하고, `index`는 `0`으로 지정하였습니다. 이 말은 이 장비가 첫 번째 워커이고, 따라서 치프 워커이며, 다른 워커보다 더 많은 일을 하게 된다는 뜻입니다. 물론 다른 장비들에도 `TF_CONFIG` 환경변수가 설정되어야 합니다. 다른 장비들에도 `cluster`에는 동일한 딕셔너리를 지정하겠지만, `task`에는 각 장비의 역할에 따라 다른 작업 `type`이나 `index`를 지정해야 합니다.\n",
        "\n",
        "예시를 위하여, 이 튜토리얼에서는 두 개의 워커를 `localhost`에 띄우는 방법을 보여드리겠습니다. 실제로는 각 워커를 다른 장비에서 띄울텐데, 실제 IP 주소와 포트를 할당하고, 그에 맞게 `TF_CONFIG`를 지정해야 합니다.\n",
        "\n",
        "주의: 아래 코드를 코랩에서 실행하지 마십시오. 텐서플로 런타임이 주어진 IP와 포트로 gRPC 서버를 띄우려고 할 텐데, 아마도 실패할 것입니다.\n",
        "\n",
        "```\n",
        "os.environ['TF_CONFIG'] = json.dumps({\n",
        "    'cluster': {\n",
        "        'worker': [\"localhost:12345\", \"localhost:23456\"]\n",
        "    },\n",
        "    'task': {'type': 'worker', 'index': 0}\n",
        "})\n",
        "```\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "P94PrIW_kSCE"
      },
      "source": [
        "이 예제에서는 학습률을 바꾸지 않고 그대로 사용한 것에 주의하십시오. 실제로는 전역(global) 배치 크기에 따라 학습률을 조정해야 할 수 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "UhNtHfuxCGVy"
      },
      "source": [
        "## 적절한 전략 고르기\n",
        "\n",
        "텐서플로의 분산 전략은 크게 각 훈련 단계가 워커들이 가진 복제본들끼리 동기화되는 동기 훈련 방식과, 동기화가 엄격하게 이루어지지 않는 비동기 훈련 방식이 있습니다.\n",
        "\n",
        "이 튜토리얼에서 다루는 `MultiWorkerMirroredStrategy`는 동기 다중 워커 훈련에서 추천하는 전략입니다.\n",
        "모델을 훈련하려면 `tf.distribute.experimental.MultiWorkerMirroredStrategy` 인스턴스를 하나 만드십시오.\n",
        "`MultiWorkerMirroredStrategy`는 모델의 레이어에 있는 모든 변수의 복사본을 각 워커의 장치마다 만듭니다. 그리고 수집 작업을 위한 텐서플로 연산인 `CollectiveOps`를 사용하여 그래디언트를 모으고, 각 변수의 값을 동기화합니다. [`tf.distribute.Strategy` 가이드](../../guide/distributed_training.ipynb)에 이 전략에 대한 더 자세한 설명이 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "1uFSHCJXMrQ-",
        "colab": {}
      },
      "source": [
        "strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "N0iv7SyyAohc"
      },
      "source": [
        "Note: `MultiWorkerMirroredStrategy.__init__()`가 호출될 때, `TF_CONFIG`를 파싱하고 텐서플로 gRPC 서버가 구동됩니다. 따라서 `TF_CONFIG` 환경변수는 `tf.distribute.Strategy` 인스턴스를 만들기 전에 설정해야 합니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "FMy2VM4Akzpr"
      },
      "source": [
        "`MultiWorkerMirroredStrategy` 는 [`CollectiveCommunication`](https://github.com/tensorflow/tensorflow/blob/a385a286a930601211d78530734368ccb415bee4/tensorflow/python/distribute/cross_device_ops.py#L928) 매개변수로 선택할 수 있는 여러 가지 구현체를 제공합니다. `RING`(링)은 링 구조 기반의 수집 작업 구현체이고, 장비 간 통신을 위하여 gRPC를 사용합니다.  `NCCL`은 [Nvidia의 NCCL](https://developer.nvidia.com/nccl)로 수집 작업을 구현한 것입니다. `AUTO`를 지정하면, 런타임이 알아서 선택합니다. 어떤 수집 작업 구현체가 최적인지는 GPU의 종류와 수, 클러스터 내 네트워크 연결 등 여러 요소에 따라 달라집니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "H47DDcOgfzm7"
      },
      "source": [
        "## MultiWorkerMirroredStrategy로 모델 훈련하기\n",
        "\n",
        "다중 워커 분산 훈련을 위하여 `tf.distribute.Strategy` API를 `tf.keras`와 함께 사용하려면, 딱 한 가지만 바꾸면 됩니다. 바로 모델 구성과 `model.compile()` 호출 코드를 `strategy.scope()` 안으로 넣는 것입니다. 분산 전략의 범위(scope)를 써서 변수를 어디에 어떻게 만들지 지정할 수 있습니다. `MultiWorkerMirroredStrategy`의 경우, 만들어지는 변수는 `MirroredVariable`이고, 각 워커에 복제본이 생깁니다.\n",
        "\n",
        "\n",
        "Note: 아래 코드가 예상과 같이 동작하는 것처럼 보이겠지만, 사실은 단일 워커로 동작하는 것입니다. `TF_CONFIG`가 설정되어 있지 않기 때문입니다. 실제로 `TF_CONFIG`를 설정하고 아래 예제를 실행하면, 여러 장비를 활용하여 훈련 속도가 빨라지는 것을 볼 수 있습니다."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "BcsuBYrpgnlS",
        "colab": {}
      },
      "source": [
        "NUM_WORKERS = 2\n",
        "# 여기서 배치 크기는 워커의 수를 곱한 크기로 늘려야 합니다. `tf.data.Dataset.batch`에는\n",
        "# 전역 배치 크기를 지정해야 하기 때문입니다. 전에는 64였지만, 이제 128이 됩니다.\n",
        "GLOBAL_BATCH_SIZE = 64 * NUM_WORKERS\n",
        "train_datasets = train_datasets_unbatched.batch(GLOBAL_BATCH_SIZE)\n",
        "with strategy.scope():\n",
        "  multi_worker_model = build_and_compile_cnn_model()\n",
        "multi_worker_model.fit(x=train_datasets, epochs=3)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Rr14Vl9GR4zq"
      },
      "source": [
        "### 데이터셋 샤딩과 배치 크기\n",
        "다중 워커 훈련에서는 수렴과 성능을 위하여 데이터를 여러 부분으로 샤딩(sharding)해야 합니다. 하지만, 위 코드 예에서는 데이터셋을 샤딩하지 않고 바로 `model.fit()`으로 보낸 것을 볼 수 있습니다. 이는 `tf.distribute.Strategy` API가 다중 워커 훈련에 맞게 자동으로 데이터셋을 샤딩해주기 때문입니다.\n",
        "\n",
        "만약 훈련할 때 샤딩을 직접 하고 싶다면, `tf.data.experimental.DistributeOptions` API를 사용해서 자동 샤딩 기능을 끌 수 있습니다. 다음과 같이 말입니다."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "JxEtdh1vH-TF",
        "colab": {}
      },
      "source": [
        "options = tf.data.Options()\n",
        "options.experimental_distribute.auto_shard = False\n",
        "train_datasets_no_auto_shard = train_datasets.with_options(options)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "NBCtYvmCH-7g"
      },
      "source": [
        "또 하나 주목할 점은 `datasets`의 배치 크기입니다. 앞서 코드에서 `GLOBAL_BATCH_SIZE = 64 * NUM_WORKERS`로 지정하였습니다. 단일 워커일 때보다 `NUM_WORKERS` 배만큼 크게 지정한 것입니다. 이는 실제로 각 워커에 전달되는 배치 크기가 `tf.data.Dataset.batch()`에 매개변수로 전달된 전역 배치 크기를 워커의 수로 나눈 것이 되기 때문입니다. 즉, 이렇게 바꾸어야 실제로 워커가 처리하는 배치 크기가 단일 워커일 때와 동일한 값이 됩니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "XVk4ftYx6JAO"
      },
      "source": [
        "## 성능\n",
        "\n",
        "이제 케라스 모델이 완성되었습니다. `MultiWorkerMirroredStrategy`를 사용하여 여러 워커를 사용하여 훈련할 수 있습니다. 다중 워커 훈련의 성능을 더 높이려면 다음 기법들을 확인해 보십시오.\n",
        "\n",
        "\n",
        "*   `MultiWorkerMirroredStrategy`는 여러 가지 [수집 작업 통신 구현체](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/python/distribute/cross_device_ops.py)를 제공합니다. `RING`(링)은 링 구조 기반의 수집 작업 구현체이고, 장비 간 통신을 위하여 gRPC를 사용합니다.  `NCCL`은 [Nvidia의 NCCL](https://developer.nvidia.com/nccl)로 수집 작업을 구현한 것입니다. `AUTO`를 지정하면, 런타임이 알아서 선택합니다. 어떤 수집 작업 구현체가 최적인지는 GPU의 종류와 수, 클러스터 내 네트워크 연결 등 여러 요소에 따라 달라집니다. 런타임이 알아서 선택한 것을 바꾸려면, `MultiWorkerMirroredStrategy` 생성자의 `communication` 매개변수에 적절한 값을 지정하십시오. 예를 들면 `communication=tf.distribute.experimental.CollectiveCommunication.NCCL`과 같이 지정합니다.\n",
        "*    가능하면 변수를 `tf.float` 타입으로 바꾸십시오. 공식 ResNet 모델을 보면 어떻게 바꾸는지 [예제](https://github.com/tensorflow/models/blob/8367cf6dabe11adf7628541706b660821f397dce/official/resnet/resnet_model.py#L466)가 있습니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "97WhAu8uKw3j"
      },
      "source": [
        "## 내결함성\n",
        "\n",
        "동기 훈련 방식에서는, 워커 중 하나가 죽으면 전체 클러스터가 죽어버리고, 복구 메커니즘이 따로 없습니다. 하지만 케라스와 `tf.distribute.Strategy`를 함께 사용하면, 워커가 죽거나 불안정해지는 경우에도 내결함성을 제공합니다. 이는 사용자가 선택한 분산 파일 시스템에 훈련 상태를 저장하는 기능을 제공하기 때문입니다. 기존 인스턴스가 죽거나 정지당해서 재시작되는 경우에도 훈련 상태를 복구할 수 있습니다.\n",
        "\n",
        "모든 워커가 훈련 에포크 혹은 스텝에 따라 동기화되므로, 다른 워커들은 죽거나 정지당한 워커가 복구될 때까지 기다려야 합니다.\n",
        "\n",
        "### ModelCheckpoint 콜백\n",
        "\n",
        "다중 워커 훈련의 내결함 기능을 사용하려면, `tf.keras.Model.fit()`를 호출할 때 `tf.keras.callbacks.ModelCheckpoint`의 인스턴스를 제공해야 합니다. 이 콜백이 체크포인트와 훈련 상태를 `ModelCheckpoint`의 `filepath` 매개변수에 지정한 디렉터리에 저장합니다."
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "colab_type": "code",
        "id": "xIY9vKnUU82o",
        "colab": {}
      },
      "source": [
        "# `filepath` 매개변수를 모든 워커가 접근할 수 있는 파일 시스템 경로로 바꾸십시오.\n",
        "callbacks = [tf.keras.callbacks.ModelCheckpoint(filepath='/tmp/keras-ckpt')]\n",
        "with strategy.scope():\n",
        "  multi_worker_model = build_and_compile_cnn_model()\n",
        "multi_worker_model.fit(x=train_datasets, epochs=3, callbacks=callbacks)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "Ii6VmEdOjkZr"
      },
      "source": [
        "워커가 정지당하면, 정지당한 워커가 다시 살아날 때까지 전체 클러스터가 잠시 멈춥니다. 워커가 클러스터에 다시 들어오면, 다른 워커도 재시작됩니다. 모든 워커가 이전에 저장한 체크포인트 파일을 읽고, 예전 상태를 불러오면 클러스터가 다시 일관된 상태가 됩니다. 그리고서 훈련이 재개됩니다.\n",
        "\n",
        "`ModelCheckpoint`의 `filepath`가 위치한 디렉터리를 살펴보면, 임시로 생성된 체크포인트 파일들을 확인할 수 있을 것입니다. 이 파일들은 실패한 작업을 복구하는데 필요한 것들로, 다중 워커 훈련 작업을 성공적으로 마치고 나면 `tf.keras.Model.fit()` 함수가 끝날 때 라이브러리가 알아서 삭제할 것입니다."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "colab_type": "text",
        "id": "ega2hdOQEmy_"
      },
      "source": [
        "## 참조\n",
        "1. [텐서플로로 분산 훈련하기](https://www.tensorflow.org/guide/distributed_training) 가이드는 사용 가능한 분산 전략들을 개괄하고 있습니다.\n",
        "2. 공식 [ResNet50](https://github.com/tensorflow/models/blob/master/official/resnet/imagenet_main.py) 모델은 `MirroredStrategy`나 `MultiWorkerMirroredStrategy`로 훈련할 수 있습니다."
      ]
    }
  ]
}